# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: site-packages\PyInstaller\depend\bindepend.py
"""
Find external dependencies of binary libraries.
"""
import ctypes.util, os, re, sys
from glob import glob
import zipfile, collections
from .. import compat
from ..compat import (
    is_win,
    is_unix,
    is_aix,
    is_solar,
    is_cygwin,
    is_hpux,
    is_darwin,
    is_freebsd,
    is_venv,
    base_prefix,
    PYDYLIB_NAMES,
)
from . import dylib, utils
from .. import log as logging
from ..utils.win32 import winutils

logger = logging.getLogger(__name__)
seen = set()
if is_win:
    from ..utils.win32.winmanifest import RT_MANIFEST
    from ..utils.win32.winmanifest import GetManifestResources
    from ..utils.win32.winmanifest import Manifest
    from ..utils.win32 import winresource
    import pefile

    pefile.fast_load = True


def getfullnameof(mod, xtrapath=None):
    """
    Return the full path name of MOD.
    
    MOD is the basename of a dll or pyd.
    XTRAPATH is a path or list of paths to search first.
    Return the full path name of MOD.
    Will search the full Windows search path, as well as sys.path
    """
    from distutils.sysconfig import get_python_lib

    numpy_core_paths = [os.path.join(get_python_lib(), "numpy", "core")]
    if is_venv:
        numpy_core_paths.append(
            os.path.join(base_prefix, "Lib", "site-packages", "numpy", "core")
        )
    epath = sys.path + numpy_core_paths + winutils.get_system_path()
    if xtrapath is not None:
        if type(xtrapath) == type(""):
            epath.insert(0, xtrapath)
        else:
            epath = xtrapath + epath
        for p in epath:
            npth = os.path.join(p, mod)
            if os.path.exists(npth):
                if matchDLLArch(npth):
                    return npth
                for p in epath:
                    npth = os.path.join(p, mod.lower())
                    if os.path.exists(npth):
                        if matchDLLArch(npth):
                            return npth

        return ""


def _getImports_pe(pth):
    """
    Find the binary dependencies of PTH.
    
    This implementation walks through the PE header
    and uses library pefile for that and supports
    32/64bit Windows
    """
    dlls = set()
    pe = pefile.PE(pth, fast_load=True)
    pe.parse_data_directories(
        directories=[
            pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"],
            pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXPORT"],
        ],
        forwarded_exports_only=True,
        import_dllnames_only=True,
    )
    for entry in getattr(pe, "DIRECTORY_ENTRY_IMPORT", []):
        dll_str = winutils.convert_dll_name_to_str(entry.dll)
        dlls.add(dll_str)

    exportSymbols = getattr(pe, "DIRECTORY_ENTRY_EXPORT", None)
    if exportSymbols:
        for sym in exportSymbols.symbols:
            if sym.forwarder is not None:
                forwarder = winutils.convert_dll_name_to_str(sym.forwarder)
                dll, _ = forwarder.split(".")
                dlls.add(dll + ".dll")

    pe.close()
    return dlls


def _extract_from_egg(toc):
    """
    Ensure all binary modules in zipped eggs get extracted and
    included with the frozen executable.
    
    return  modified table of content
    """
    new_toc = []
    for item in toc:
        modname, pth, typ = item
        if not os.path.isfile(pth):
            pth = check_extract_from_egg(pth)[0][0]
        new_toc.append((modname, pth, typ))

    return new_toc


BindingRedirect = collections.namedtuple(
    "BindingRedirect", "name language arch oldVersion newVersion publicKeyToken"
)


def match_binding_redirect(manifest, redirect):
    return all(
        [
            manifest.name == redirect.name,
            manifest.version == redirect.oldVersion,
            manifest.language == redirect.language,
            manifest.processorArchitecture == redirect.arch,
            manifest.publicKeyToken == redirect.publicKeyToken,
        ]
    )


_exe_machine_type = None


def matchDLLArch(filename):
    """
    Return True if the DLL given by filename matches the CPU type/architecture of the
    Python process running PyInstaller.
    
    Always returns True on non-Windows platforms
    
    :param filename:
    :type filename:
    :return:
    :rtype:
    """
    global _exe_machine_type
    if not is_win:
        return True
    else:
        if _exe_machine_type is None:
            exe_pe = pefile.PE(sys.executable, fast_load=True)
            _exe_machine_type = exe_pe.FILE_HEADER.Machine
            exe_pe.close()
        pe = pefile.PE(filename, fast_load=True)
        match_arch = pe.FILE_HEADER.Machine == _exe_machine_type
        pe.close()
        return match_arch


def Dependencies(lTOC, xtrapath=None, manifest=None, redirects=None):
    """
    Expand LTOC to include all the closure of binary dependencies.
    
    `LTOC` is a logical table of contents, ie, a seq of tuples (name, path).
    Return LTOC expanded by all the binary dependencies of the entries
    in LTOC, except those listed in the module global EXCLUDES
    
    `manifest` may be a winmanifest.Manifest instance for a program manifest, so
    that all dependent assemblies of python.exe can be added to the built exe.
    
    `redirects` may be a list. Any assembly redirects found via policy files will
    be added to the list as BindingRedirect objects so they can later be used
    to modify any manifests that reference the redirected assembly.
    """
    lTOC = _extract_from_egg(lTOC)
    for nm, pth, typ in lTOC:
        if nm.upper() in seen:
            continue
        logger.debug("Analyzing %s", pth)
        seen.add(nm.upper())
        if is_win:
            for ftocnm, fn in getAssemblyFiles(pth, manifest, redirects):
                lTOC.append((ftocnm, fn, "BINARY"))

        for lib, npth in selectImports(pth, xtrapath):
            if not lib.upper() in seen:
                if npth.upper() in seen:
                    pass
                else:
                    seen.add(npth.upper())
                    lTOC.append((lib, npth, "BINARY"))

    return lTOC


def pkg_resources_get_default_cache():
    """
    Determine the default cache location
    
    This returns the ``PYTHON_EGG_CACHE`` environment variable, if set.
    Otherwise, on Windows, it returns a 'Python-Eggs' subdirectory of the
    'Application Data' directory.  On all other systems, it's '~/.python-eggs'.
    """
    egg_cache = compat.getenv("PYTHON_EGG_CACHE")
    if egg_cache is not None:
        return egg_cache
    if os.name != "nt":
        return os.path.expanduser("~/.python-eggs")
    app_data = "Application Data"
    app_homes = [
        (("APPDATA",), None),
        (("USERPROFILE",), app_data),
        (("HOMEDRIVE", "HOMEPATH"), app_data),
        (("HOMEPATH",), app_data),
        (("HOME",), None),
        (("WINDIR",), app_data),
    ]
    for keys, subdir in app_homes:
        dirname = ""
        for key in keys:
            if key in os.environ:
                dirname = os.path.join(dirname, compat.getenv(key))
            else:
                break
        else:
            if subdir:
                dirname = os.path.join(dirname, subdir)
            return os.path.join(dirname, "Python-Eggs")

    else:
        raise RuntimeError("Please set the PYTHON_EGG_CACHE enviroment variable")


def check_extract_from_egg(pth, todir=None):
    r"""
    Check if path points to a file inside a python egg file, extract the
    file from the egg to a cache directory (following pkg_resources
    convention) and return [(extracted path, egg file path, relative path
    inside egg file)].
    Otherwise, just return [(original path, None, None)].
    If path points to an egg file directly, return a list with all files
    from the egg formatted like above.
    
    Example:
    >>> check_extract_from_egg(r'C:\Python26\Lib\site-packages\my.egg\mymodule\my.pyd')
    [(r'C:\Users\UserName\AppData\Roaming\Python-Eggs\my.egg-tmp\mymodule\my.pyd',
    r'C:\Python26\Lib\site-packages\my.egg', r'mymodule/my.pyd')]
    """
    rv = []
    if os.path.altsep:
        pth = pth.replace(os.path.altsep, os.path.sep)
    components = pth.split(os.path.sep)
    for i, name in enumerate(components):
        eggpth = name.lower().endswith(".egg") and os.path.sep.join(components[: i + 1])
        if os.path.isfile(eggpth):
            try:
                egg = zipfile.ZipFile(eggpth)
            except zipfile.BadZipfile as e:
                raise SystemExit("Error: %s %s" % (eggpth, e))

            if todir is None:
                todir = os.path.join(pkg_resources_get_default_cache(), name + "-tmp")
            if components[i + 1 :]:
                members = [("/").join(components[i + 1 :])]
            else:
                members = egg.namelist()
            for member in members:
                pth = os.path.join(todir, member)
                if not os.path.isfile(pth):
                    dirname = os.path.dirname(pth)
                    if not os.path.isdir(dirname):
                        os.makedirs(dirname)
                    f = open(pth, "wb")
                    f.write(egg.read(member))
                    f.close()
                rv.append((pth, eggpth, member))

            return rv

    return [(pth, None, None)]


def getAssemblies(pth):
    """
    On Windows return the dependent Side-by-Side (SxS) assemblies of a binary as a
    list of Manifest objects.
    
    Dependent assemblies are required only by binaries compiled with MSVC 9.0.
    Python 2.7 and 3.2 is compiled with MSVC 9.0 and thus depends on Microsoft
    Redistributable runtime libraries 9.0.
    
    Python 3.3+ is compiled with version 10.0 and does not use SxS assemblies.
    """
    if pth.lower().endswith(".manifest"):
        return []
    else:
        manifestnm = pth + ".manifest"
        if os.path.isfile(manifestnm):
            fd = open(manifestnm, "rb")
            res = {RT_MANIFEST: {1: {0: fd.read()}}}
            fd.close()
        else:
            try:
                res = GetManifestResources(pth)
            except winresource.pywintypes.error as exc:
                if exc.args[0] == winresource.ERROR_BAD_EXE_FORMAT:
                    logger.info("Cannot get manifest resource from non-PE file %s", pth)
                    return []
                raise

            rv = []
            if RT_MANIFEST in res and len(res[RT_MANIFEST]):
                for name in res[RT_MANIFEST]:
                    for language in res[RT_MANIFEST][name]:
                        try:
                            manifest = Manifest()
                            manifest.filename = (":").join(
                                [pth, str(RT_MANIFEST), str(name), str(language)]
                            )
                            manifest.parse_string(
                                res[RT_MANIFEST][name][language], False
                            )
                        except Exception as exc:
                            logger.error(
                                "Can not parse manifest resource %s, %s from %s",
                                name,
                                language,
                                pth,
                                exc_info=1,
                            )
                        else:
                            if manifest.dependentAssemblies:
                                logger.debug("Dependent assemblies of %s:", pth)
                                logger.debug(
                                    (", ").join(
                                        [
                                            assembly.getid()
                                            for assembly in manifest.dependentAssemblies
                                        ]
                                    )
                                )

                        rv.extend(manifest.dependentAssemblies)

        return rv


def getAssemblyFiles(pth, manifest=None, redirects=None):
    """
    Find all assemblies that are dependencies of the given binary and return the files
    that make up the assemblies as (name, fullpath) tuples.
    
    If a WinManifest object is passed as `manifest`, also updates that manifest to
    reference the returned assemblies. This is done only to update the built app's .exe
    with the dependencies of python.exe
    
    If a list is passed as `redirects`, and binding redirects in policy files are
    applied when searching for assemblies, BindingRedirect objects are appended to this
    list.
    
    Return a list of pairs (name, fullpath)
    """
    rv = []
    if manifest:
        _depNames = set((dep.name for dep in manifest.dependentAssemblies))
    for assembly in getAssemblies(pth):
        if assembly.getid().upper() in seen:
            continue
        if manifest:
            if assembly.name not in _depNames:
                logger.info(
                    "Adding %s to dependent assemblies of final executable\n  required by %s",
                    assembly.name,
                    pth,
                )
                manifest.dependentAssemblies.append(assembly)
                _depNames.add(assembly.name)
        if not dylib.include_library(assembly.name):
            logger.debug("Skipping assembly %s", assembly.getid())
            continue
            if assembly.optional:
                logger.debug("Skipping optional assembly %s", assembly.getid())
                continue
                from ..config import CONF

                if CONF.get("win_no_prefer_redirects"):
                    files = assembly.find_files()
                else:
                    files = []
                if not len(files):
                    files = assembly.find_files(ignore_policies=False)
                    if len(files) and redirects is not None:
                        old_version = assembly.version
                        new_version = assembly.get_policy_redirect()
                        logger.info(
                            "Adding redirect %s version %s -> %s",
                            assembly.name,
                            old_version,
                            new_version,
                        )
                        redirects.append(
                            BindingRedirect(
                                name=assembly.name,
                                language=assembly.language,
                                arch=assembly.processorArchitecture,
                                publicKeyToken=assembly.publicKeyToken,
                                oldVersion=old_version,
                                newVersion=new_version,
                            )
                        )
                if files:
                    seen.add(assembly.getid().upper())
                    for fn in files:
                        fname, fext = os.path.splitext(fn)
                        if fext.lower() == ".manifest":
                            nm = assembly.name + fext
                        else:
                            nm = os.path.basename(fn)
                        ftocnm = nm
                        if assembly.language not in (None, "", "*", "neutral"):
                            ftocnm = os.path.join(assembly.getlanguage(), ftocnm)
                        nm, ftocnm, fn = [
                            item.encode(sys.getfilesystemencoding())
                            for item in (nm, ftocnm, fn)
                        ]
                        if fn.upper() not in seen:
                            logger.debug("Adding %s", ftocnm)
                            seen.add(nm.upper())
                            seen.add(fn.upper())
                            rv.append((ftocnm, fn))

                else:
                    logger.error("Assembly %s not found", assembly.getid())

    new_rv = []
    for item in rv:
        a = item[0].decode("ascii")
        b = item[1].decode("ascii")
        new_rv.append((a, b))

    rv = new_rv
    return rv


def selectImports(pth, xtrapath=None):
    """
    Return the dependencies of a binary that should be included.
    
    Return a list of pairs (name, fullpath)
    """
    rv = []
    if xtrapath is None:
        xtrapath = [os.path.dirname(pth)]
    else:
        if not isinstance(xtrapath, list):
            raise AssertionError
        xtrapath = [os.path.dirname(pth)] + xtrapath
    dlls = getImports(pth)
    for lib in dlls:
        if lib.upper() in seen:
            continue
        if not is_win:
            if not is_cygwin:
                npth = lib
                lib = os.path.basename(lib)
            npth = getfullnameof(lib, xtrapath)
        if npth:
            candidatelib = npth
        else:
            candidatelib = lib
        if not dylib.include_library(candidatelib):
            if candidatelib.find("libpython") < 0:
                pass
        if candidatelib.find("Python.framework") < 0:
            if npth.upper() not in seen:
                logger.debug("Skipping %s dependency of %s", lib, os.path.basename(pth))
                continue
            if npth:
                if npth.upper() not in seen:
                    logger.debug(
                        "Adding %s dependency of %s from %s",
                        lib,
                        os.path.basename(pth),
                        npth,
                    )
                    rv.append((lib, npth))
                else:
                    logger.warning("lib not found: %s dependency of %s", lib, pth)

    return rv


def _getImports_ldd(pth):
    """
    Find the binary dependencies of PTH.
    
    This implementation is for ldd platforms (mostly unix).
    """
    rslt = set()
    if is_aix:
        lddPattern = re.compile(
            "^\\s*(((?P<libarchive>(.*\\.a))(?P<objectmember>\\(.*\\)))|((?P<libshared>(.*\\.so))))$"
        )
    else:
        if is_hpux:
            lddPattern = re.compile("^\\s+(.*)\\s+=>\\s+(.*)$")
        else:
            if is_solar:
                lddPattern = re.compile("^\\s+(.*)\\s+=>\\s+(.*)$")
            else:
                lddPattern = re.compile("\\s*(.*?)\\s+=>\\s+(.*?)\\s+\\(.*\\)")
            for line in compat.exec_command("ldd", pth).splitlines():
                m = lddPattern.search(line)
                if m:
                    if is_aix:
                        libarchive = m.group("libarchive")
                        if libarchive:
                            lib = libarchive
                            name = os.path.basename(lib) + m.group("objectmember")
                        else:
                            lib = m.group("libshared")
                            name = os.path.basename(lib)
                    else:
                        if is_hpux:
                            name, lib = m.group(1), m.group(2)
                        else:
                            name, lib = m.group(1), m.group(2)
                    if name[:10] in ("linux-gate", "linux-vdso"):
                        pass
                    else:
                        if os.path.exists(lib):
                            if lib not in rslt:
                                rslt.add(lib)
                            else:
                                logger.error(
                                    "Can not find %s in path %s (needed by %s)",
                                    name,
                                    lib,
                                    pth,
                                )

            return rslt


def _getImports_macholib(pth):
    """
    Find the binary dependencies of PTH.
    
    This implementation is for Mac OS X and uses library macholib.
    """
    from macholib.MachO import MachO
    from macholib.mach_o import LC_RPATH
    from macholib.dyld import dyld_find

    rslt = set()
    seen = set()
    m = MachO(pth)
    for header in m.headers:
        for idx, name, lib in header.walkRelocatables():
            if lib not in seen:
                seen.add(lib)

    run_paths = set()
    for header in m.headers:
        for command in header.commands:
            cmd_type = command[0].cmd
            if cmd_type == LC_RPATH:
                rpath = command[2].decode("utf-8")
                rpath = rpath.rstrip("\x00")
                executable_path = os.path.dirname(pth)
                rpath = re.sub(
                    "^@(executable_path|loader_path|rpath)(/|$)",
                    executable_path + "\\2",
                    rpath,
                )
                rpath = os.path.normpath(os.path.join(executable_path, rpath))
                run_paths.update([rpath])
            elif ".framework" in pth:
                run_paths.update(["../../../"])

    run_paths.add(os.path.join(base_prefix, "lib"))
    exec_path = os.path.abspath(os.path.dirname(pth))
    for lib in seen:
        if lib.startswith("@rpath"):
            lib = lib.replace("@rpath", ".")
            final_lib = None
            for run_path in run_paths:
                if not os.path.isabs(run_path):
                    run_path = os.path.join(exec_path, run_path)
                if os.path.exists(os.path.join(run_path, lib)):
                    final_lib = os.path.abspath(os.path.join(run_path, lib))
                    rslt.add(final_lib)
                    break

            if not final_lib:
                logger.error("Can not find path %s (needed by %s)", lib, pth)
            else:
                if lib.startswith("@loader_path"):
                    lib = lib.replace("@loader_path", "@executable_path")
            try:
                lib = dyld_find(lib, executable_path=exec_path)
                rslt.add(lib)
            except ValueError:
                logger.error("Can not find path %s (needed by %s)", lib, pth)

    return rslt


def getImports(pth):
    """
    Forwards to the correct getImports implementation for the platform.
    """
    if is_win or is_cygwin:
        if pth.lower().endswith(".manifest"):
            return []
            try:
                return _getImports_pe(pth)
            except Exception as exception:
                logger.warning(
                    "Can not get binary dependencies for file: %s", pth, exc_info=1
                )
                return []

        else:
            if is_darwin:
                return _getImports_macholib(pth)
            return _getImports_ldd(pth)


def findLibrary(name):
    """
    Look for a library in the system.
    
    Emulate the algorithm used by dlopen.
    `name`must include the prefix, e.g. ``libpython2.4.so``
    """
    if not is_unix:
        raise AssertionError(
            "Current implementation for Unix only (Linux, Solaris, AIX, FreeBSD)"
        )
    lib = None
    if is_aix:
        lp = compat.getenv("LIBPATH", "")
    else:
        if is_darwin:
            lp = compat.getenv("DYLD_LIBRARY_PATH", "")
        else:
            lp = compat.getenv("LD_LIBRARY_PATH", "")
        for path in lp.split(os.pathsep):
            libs = glob(os.path.join(path, name + "*"))
            if libs:
                lib = libs[0]
                break

    if lib is None:
        utils.load_ldconfig_cache()
        lib = utils.LDCONFIG_CACHE.get(name)
        if lib:
            if not os.path.isfile(lib):
                raise AssertionError
    if lib is None:
        paths = ["/lib", "/usr/lib"]
        arch = compat.architecture()
        if arch == "32bit":
            paths.extend(["/lib32", "/usr/lib32", "/usr/lib/i386-linux-gnu"])
        else:
            paths.extend(["/lib64", "/usr/lib64", "/usr/lib/x86_64-linux-gnu"])
        try:
            import sysconfig

            arch_subdir = sysconfig.get_config_var("multiarchsubdir")
            if arch_subdir:
                arch_subdir = os.path.basename(arch_subdir)
                paths.append(os.path.join("/usr/lib", arch_subdir))
            else:
                logger.debug("Multiarch directory not detected.")
        except ImportError:
            logger.debug("Multiarch directory not detected.")

        if is_aix:
            paths.append("/opt/freeware/lib")
        else:
            if is_hpux:
                if arch == "32bit":
                    paths.append("/usr/local/lib/hpux32")
                else:
                    paths.append("/usr/local/lib/hpux64")
            else:
                if is_freebsd:
                    paths.append("/usr/local/lib")
            for path in paths:
                libs = glob(os.path.join(path, name + "*"))
                if libs:
                    lib = libs[0]
                    break

        if lib is None:
            return
        if is_freebsd or is_aix:
            return lib
        dir = os.path.dirname(lib)
        return os.path.join(dir, _get_so_name(lib))


def _get_so_name(filename):
    """
    Return the soname of a library.
    
    Soname is usefull whene there are multiple symplinks to one library.
    """
    cmd = ["objdump", "-p", filename]
    m = re.search("\\s+SONAME\\s+([^\\s]+)", compat.exec_command(*cmd))
    return m.group(1)


def get_python_library_path():
    """
    Find dynamic Python library that will be bundled with frozen executable.
    
    NOTOE: This is a fallback option when Python library is probably linked
    statically with the Python executable and we need to search more for it.
    On Debian/Ubuntu this is the case.
    
    Return  full path to Python dynamic library or None when not found.
    
    
    We need to know name of the Python dynamic library for the bootloader.
    Bootloader has to know what library to load and not trying to guess.
    
    Some linux distributions (e.g. debian-based) statically build the
    Python executable to the libpython, so bindepend doesn't include
    it in its output. In this situation let's try to find it.
    
    Darwin custom builds could possibly also have non-framework style libraries,
    so this method also checks for that variant as well.
    """
    dlls = getImports(sys.executable)
    for filename in dlls:
        for name in PYDYLIB_NAMES:
            if os.path.basename(filename) == name:
                if is_win:
                    if not os.path.isabs(filename):
                        filename = getfullnameof(filename)
                return filename

    if is_unix:
        for name in PYDYLIB_NAMES:
            python_libname = findLibrary(name)
            if python_libname:
                return python_libname

    else:
        if is_darwin:
            prefixes = [compat.base_prefix, os.path.join(compat.base_prefix, "lib")]
            for prefix in prefixes:
                for name in PYDYLIB_NAMES:
                    full_path = os.path.join(prefix, name)
                    if os.path.exists(full_path):
                        return full_path


def findSystemLibrary(name):
    """
        Given a library name, try to resolve the path to that library. If the
        path is already an absolute path, return that without searching.
    """
    if os.path.isabs(name):
        return name
    elif is_unix:
        return findLibrary(name)
    elif is_win:
        return getfullnameof(name)
    else:
        return ctypes.util.find_library(name)
